package com.yun.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {

	/**
	 * 获取接口的所有实现类 理论上也可以用来获取类的所有子类
	 * 查询路径有限制，只局限于接口所在模块下，比如pandora-gateway,而非整个pandora（会递归搜索该文件夹下所以的实现类）
	 * 路径中不可含中文，否则会异常。若要支持中文路径，需对该模块代码中url.getPath() 返回值进行urldecode.
	 * Created by wangzhen3 on 2017/6/23.
	 */
	private static final Logger LOG = LoggerFactory.getLogger(ClassUtil.class);

	public static ArrayList<Class<?>> getAllClassByInterface(Class<?> clazz) {
		ArrayList<Class<?>> list = new ArrayList<>();
		// 判断是否是一个接口
		if (clazz.isInterface()) {
			try {
				ArrayList<Class<?>> allClass = getAllClass(clazz.getPackage().getName());
				/**
				 * 循环判断路径下的所有类是否实现了指定的接口 并且排除接口类自己
				 */
				for (int i = 0; i < allClass.size(); i++) {
					/**
					 * 判断是不是同一个接口
					 */
					// isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class
					// 参数所表示的类或接口是否相同，或是否是其超类或超接口
					if (clazz.isAssignableFrom(allClass.get(i))) {
						if (!clazz.equals(allClass.get(i))) {
							// 自身并不加进去
							list.add(allClass.get(i));
						}
					}
				}
			} catch (Exception e) {
				LOG.error("出现异常{}", e.getMessage());
				throw new RuntimeException("出现异常" + e.getMessage());
			}
		}
		LOG.info("class list size :" + list.size());
		return list;
	}
	
	
	// 获取指定包名下的所有类（可根据注解进行过滤）
    public static List<Class<?>> getClassListByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath();
                        addClassByAnnotation(classList, packagePath, packageName, annotationClass);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            if (jarEntryName.endsWith(".class")) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                Class<?> cls = Class.forName(className);
                                if (cls.isAnnotationPresent(annotationClass)) {
                                    classList.add(cls);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }
    
    private static void addClassByAnnotation(List<Class<?>> classList, String packagePath, String packageName, Class<? extends Annotation> annotationClass) {
        try {
            File[] files = getClassFiles(packagePath);
            if (files != null) {
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isFile()) {
                        String className = getClassName(packageName, fileName);
                        Class<?> cls = Class.forName(className);
                        if (cls.isAnnotationPresent(annotationClass)) {
                            classList.add(cls);
                        }
                    } else {
                        String subPackagePath = getSubPackagePath(packagePath, fileName);
                        String subPackageName = getSubPackageName(packageName, fileName);
                        addClassByAnnotation(classList, subPackagePath, subPackageName, annotationClass);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private static File[] getClassFiles(String packagePath) {
        return new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
    }

    private static String getClassName(String packageName, String fileName) {
        String className = fileName.substring(0, fileName.lastIndexOf("."));
        if (!(packageName == null)) {
            className = packageName + "." + className;
        }
        return className;
    }

    private static String getSubPackagePath(String packagePath, String filePath) {
        String subPackagePath = filePath;
        if (!(packagePath == null)) {
            subPackagePath = packagePath + "/" + subPackagePath;
        }
        return subPackagePath;
    }

    private static String getSubPackageName(String packageName, String filePath) {
        String subPackageName = filePath;
        if (!(packageName == null)) {
            subPackageName = packageName + "." + subPackageName;
        }
        return subPackageName;
    }


	/**
	 * 从一个指定路径下查找所有的类
	 *
	 * @param packagename
	 */
	private static ArrayList<Class<?>> getAllClass(String packagename) {
		LOG.info("packageName to search：" + packagename);
		List<String> classNameList = getClassName(packagename);
		ArrayList<Class<?>> list = new ArrayList<>();

		for (String className : classNameList) {
			try {
				list.add(Class.forName(className));
			} catch (ClassNotFoundException e) {
				LOG.error("load class from name failed:" + className + e.getMessage());
				throw new RuntimeException("load class from name failed:" + className + e.getMessage());
			}
		}
		LOG.info("find list size :" + list.size());
		return list;
	}
	
	public static Set<Class<?>> GetAllClass(String packageName){
		Set<Class<?>> classes = new HashSet<>();
        Enumeration<URL> dirs = null;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".","/"));
			while (dirs.hasMoreElements()){
				URL nextElement = dirs.nextElement();
				String protocol = nextElement.getProtocol();
				if("jar".equals(protocol)) {
					JarFile jar = ((JarURLConnection) nextElement.openConnection()).getJarFile();
					findClassesByJar(packageName, jar, classes);
				}else {
					//获取物理路径
					String filePath = nextElement.getPath();
					addPathToClasses(filePath,packageName,classes);
				}
		    }
        } catch (Exception e) {
        	e.printStackTrace();
        }
		return classes;
	}
	
	public static void findClassesByJar(String pkgName, JarFile jar, Set<Class<?>> classes) throws ClassNotFoundException {
        String pkgDir = pkgName.replace(".", "/");
        // 从此jar包 得到一个枚举类
        Enumeration<JarEntry> entry = jar.entries();
        JarEntry jarEntry;
        String name, className;
        Class<?> claze;
        // 同样的进行循环迭代
        while (entry.hasMoreElements()) {
            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文
            jarEntry = entry.nextElement();
            name = jarEntry.getName();
            // 如果是以/开头的
            if (name.charAt(0) == '/') {
                // 获取后面的字符串
                name = name.substring(1);
            }
            if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) {
                continue;
            }
            //如果是一个.class文件 而且不是目录
            // 去掉后面的".class" 获取真正的类名
            className = name.substring(0, name.length() - 6);
            //加载类
            claze = Thread.currentThread().getContextClassLoader().loadClass(className.replace("/", "."));
            // 添加到集合中去
            if (claze != null) {
                classes.add(claze);
            }
        }
    }

	public static void addPathToClasses(String classPath, String rootPackageName, Set<Class<?>> classes) {
	    File file = new File(classPath);
    	if (file.isDirectory()) {
    		File[] list = file.listFiles();
    		//如果是文件夹就需要在包名后面添加文件名
    		for (File path : list) {
    			if (path.isDirectory()) {
    				addPathToClasses(path.getAbsolutePath(), rootPackageName+"."+path.getName(), classes);
    			} else {
    				addPathToClasses(path.getAbsolutePath(), rootPackageName, classes);
    			}
    		}
    	} else {
		    if (file.getName().endsWith(".class")){
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					classes.add(Thread.currentThread().getContextClassLoader().loadClass(rootPackageName + "." + className));
	            } catch (ClassNotFoundException e) {
	            	try {
						classes.add(Thread.currentThread().getContextClassLoader().loadClass(rootPackageName.replaceAll("/", ".") + className));
					} catch (ClassNotFoundException e1) {
						e1.printStackTrace();
					}
	            }
		    }
		}
	}

	/**
	 * 获取某包下所有类
	 *
	 * @param packageName 包名
	 * @return 类的完整名称
	 */
	public static List<String> getClassName(String packageName) {

		List<String> fileNames = null;
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		String packagePath = packageName.replace(".", "/");
		URL url = loader.getResource(packagePath);
		if (url != null) {
			String type = url.getProtocol();
			LOG.debug("file type : " + type);
			if (type.equals("file")) {
				String fileSearchPath = url.getPath();
				LOG.debug("fileSearchPath: " + fileSearchPath);
				fileSearchPath = fileSearchPath.substring(0, fileSearchPath.indexOf("/classes"));
				LOG.debug("fileSearchPath: " + fileSearchPath);
				fileNames = getClassNameByFile(fileSearchPath);
			} else if (type.equals("jar")) {
				try {
					JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
					JarFile jarFile = jarURLConnection.getJarFile();
					fileNames = getClassNameByJar(jarFile, packagePath);
				} catch (IOException e) {
					throw new RuntimeException("open Package URL failed：" + e.getMessage());
				}

			} else {
				throw new RuntimeException("file system not support! cannot load MsgProcessor！");
			}
		}
		return fileNames;
	}

	/**
	 * 从项目文件获取某包下所有类
	 *
	 * @param filePath 文件路径
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByFile(String filePath) {
		List<String> myClassName = new ArrayList<String>();
		File file = new File(filePath);
		File[] childFiles = file.listFiles();
		for (File childFile : childFiles) {
			if (childFile.isDirectory()) {
				myClassName.addAll(getClassNameByFile(childFile.getPath()));
			} else {
				String childFilePath = childFile.getPath();
				if (childFilePath.endsWith(".class")) {
					childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));
					childFilePath = childFilePath.replace("\\", ".");
					myClassName.add(childFilePath);
				}
			}
		}

		return myClassName;
	}

	/**
	 * 从jar获取某包下所有类
	 *
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByJar(JarFile jarFile, String packagePath) {
		List<String> myClassName = new ArrayList<String>();
		try {
			Enumeration<JarEntry> entrys = jarFile.entries();
			while (entrys.hasMoreElements()) {
				JarEntry jarEntry = entrys.nextElement();
				String entryName = jarEntry.getName();
				//LOG.info("entrys jarfile:"+entryName);
				if (entryName.endsWith(".class")) {
					entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
					myClassName.add(entryName);
					//LOG.debug("Find Class :"+entryName);
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("发生异常:" + e.getMessage());
		}
		return myClassName;
	}


	public static List<Class<?>> getClasses(String packageName) {

		// 第一个class类的集合
		List<Class<?>> classes = new ArrayList<Class<?>>();
		// 是否循环迭代
		boolean recursive = true;
		// 获取包的名字 并进行替换
		String packageDirName = packageName.replace('.', '/');
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
			// 循环迭代下去
			while (dirs.hasMoreElements()) {
				// 获取下一个元素
				URL url = dirs.nextElement();
				// 得到协议的名称
				String protocol = url.getProtocol();
				// 如果是以文件的形式保存在服务器上
				if ("file".equals(protocol)) {
					// 获取包的物理路径
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式扫描整个包下的文件 并添加到集合中
					findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
				} else if ("jar".equals(protocol)) {
					// 如果是jar包文件
					// 定义一个JarFile
					JarFile jar;
					try {
						// 获取jar
						jar = ((JarURLConnection) url.openConnection()).getJarFile();
						// 从此jar包 得到一个枚举类
						Enumeration<JarEntry> entries = jar.entries();
						// 同样的进行循环迭代
						while (entries.hasMoreElements()) {
							// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// 如果是以/开头的
							if (name.charAt(0) == '/') {
								// 获取后面的字符串
								name = name.substring(1);
							}
							// 如果前半部分和定义的包名相同
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// 如果以"/"结尾 是一个包
								if (idx != -1) {
									// 获取包名 把"/"替换成"."
									packageName = name.substring(0, idx).replace('/', '.');
								}
								// 如果可以迭代下去 并且是一个包
								if ((idx != -1) || recursive) {
									// 如果是一个.class文件 而且不是目录
									if (name.endsWith(".class") && !entry.isDirectory()) {
										// 去掉后面的".class" 获取真正的类名
										String className = name.substring(packageName.length() + 1, name.length() - 6);
										try {
											// 添加到classes
											classes.add(Class.forName(packageName + '.' + className));
										} catch (ClassNotFoundException e) {
											e.printStackTrace();
										}
									}
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return classes;
	}

	public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
	                                                    List<Class<?>> classes) {
		// 获取此包的目录 建立一个File
		File dir = new File(packagePath);
		// 如果不存在或者 也不是目录就直接返回
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}
		// 如果存在 就获取包下的所有文件 包括目录
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
			public boolean accept(File file) {
				return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
			}
		});
		if (dirfiles == null)
			return;
		// 循环所有文件
		for (File file : dirfiles) {
			// 如果是目录 则继续扫描
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
						classes);
			} else {
				// 如果是java类文件 去掉后面的.class 只留下类名
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					// 添加到集合中去
					classes.add(Class.forName(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 将数组数据转换为实体类
	 * 此处数组元素的顺序必须与实体类构造函数中的属性顺序一致
	 *
	 * @param list  数组对象集合
	 * @param clazz 实体类
	 * @param <T>   实体类
	 * @param model 实例化的实体类
	 * @return 实体类集合
	 */
	public static <T> List<T> castEntity(List<Object[]> list, Class<T> clazz, Object model) {
		List<T> returnList = new ArrayList<T>();
		if (list.isEmpty()) {
			return returnList;
		}
		//获取每个数组集合的元素个数
		Object[] co = list.get(0);

		//获取当前实体类的属性名、属性值、属性类别
		List<Map<String, Object>> attributeInfoList = getFiledsInfo(model);
		//创建属性类别数组
		Class<?>[] c2 = new Class[attributeInfoList.size()];
		//如果数组集合元素个数与实体类属性个数不一致则发生错误
		if (attributeInfoList.size() != co.length) {
			return returnList;
		}
		//确定构造方法
		for (int i = 0; i < attributeInfoList.size(); i++) {
			c2[i] = (Class<?>) attributeInfoList.get(i).get("type");
		}
		try {
			for (Object[] o : list) {
				Constructor<T> constructor = clazz.getConstructor(c2);
				returnList.add(constructor.newInstance(o));
			}
		} catch (Exception ex) {
			return returnList;
		}
		return returnList;
	}

	/**
	 * 根据属性名获取属性值
	 *
	 * @param fieldName 属性名
	 * @param modle     实体类
	 * @return 属性值
	 */
	private static Object getFieldValueByName(String fieldName, Object modle) {
		try {
			String firstLetter = fieldName.substring(0, 1).toUpperCase();
			String getter = "get" + firstLetter + fieldName.substring(1);
			Method method = modle.getClass().getMethod(getter, new Class[]{});
			return method.invoke(modle);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 获取属性类型(type)，属性名(name)，属性值(value)的map组成的list
	 *
	 * @param model 实体类
	 * @return list集合
	 */
	private static List<Map<String, Object>> getFiledsInfo(Object model) {
		Field[] fields = model.getClass().getDeclaredFields();
		List<Map<String, Object>> list = new ArrayList<>(fields.length);
		Map<String, Object> infoMap;
		for (int i = 0; i < fields.length; i++) {
			infoMap = new HashMap<>();
			infoMap.put("type", fields[i].getType());
			infoMap.put("name", fields[i].getName());
			infoMap.put("value", getFieldValueByName(fields[i].getName(), model));
			list.add(infoMap);
		}
		return list;
	}

	/**
	 * 获取同一路径下所有子类或接口实现类
	 *
	 * @param intf
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws IOException,
			ClassNotFoundException {
		List<Class<?>> classes = new ArrayList<Class<?>>();
		for (Class<?> c : getClasses(cls)) {
			if (cls.isAssignableFrom(c) && !cls.equals(c)) {
				classes.add(c);
			}
		}
		return classes;
	}

	/**
	 * 取得当前类路径下的所有类
	 *
	 * @param cls
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public static List<Class<?>> getClasses(Class<?> cls) throws IOException,
			ClassNotFoundException {
		String pk = cls.getPackage().getName();
		String path = pk.replace('.', '/');
		ClassLoader classloader = Thread.currentThread().getContextClassLoader();
		URL url = classloader.getResource(path);
		return getClasses(new File(url.getFile()), pk);
	}

	/**
	 * 迭代查找类
	 *
	 * @param dir
	 * @param pk
	 * @return
	 * @throws ClassNotFoundException
	 */
	private static List<Class<?>> getClasses(File dir, String pk) throws ClassNotFoundException {
		List<Class<?>> classes = new ArrayList<Class<?>>();
		if (!dir.exists()) {
			return classes;
		}
		for (File f : dir.listFiles()) {
			if (f.isDirectory()) {
				classes.addAll(getClasses(f, pk + "." + f.getName()));
			}
			String name = f.getName();
			if (name.endsWith(".class")) {
				classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
			}
		}
		return classes;
	}

	/**
	 * 获取指定属性名的值
	 * @param fieldName	属性名
	 * @param obj		实体
	 * @return
	 */
	public static String getFieldValueByFieldName(String fieldName, Object obj) {
		try {
			//getField 只能获取public的，包括从父类继承来的字段。
			//getDeclaredField 可以获取本类所有的字段，包括private的
			Field field = obj.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			//异常 integer不能转换为string
			return String.valueOf(field.get(obj));
		} catch (Exception e) {
			LOG.debug("获取指定属性名的值异常:{}", e);
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 获取对象中除了排除列表中的所有属性
	 * @param fieldlList	要排除的属性列表
	 * @param obj			属性实体
	 * @return	key: 属性名称  value 属性值
	 */
	public static Map<String, Object> getFieldValueByFieldName(String[] fieldlList, Object obj) {
		Map<String, Object> result = new HashMap<>();
		try {
			List<String> list = new ArrayList<>(Arrays.asList(fieldlList));
			for(Field field : obj.getClass().getDeclaredFields()) {
				if ( list != null && list.contains(field.getName()) ) {
					continue;
				}
				field.setAccessible(true);
				result.put(field.getName(), field.get(obj));
			}
			return result;
		} catch (Exception e) {
			LOG.debug("将所有非空属性替换异常:{}", e);
			e.printStackTrace();
		}
		return result;
	}
	
	/**
	 * 根据属性名修改实体得值
	 * @param fieldName	要设置的属性名
	 * @param value		要设置的属性值
	 * @param t			要设置的实体
	 * @return
	 */
	public static <T> T setValueByFieldName(String fieldName, Object value, T t) {
		try {
			Field field = t.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			//field是string，value是jsonarry
			field.set(t, value.toString());
			return t;
		} catch (Exception e) {
			LOG.debug("根据属性名修改实体得值异常:{}", e);
			e.printStackTrace();
		}
		return t;
	}
	
	/**
	 * 将所有非空属性替换
	 * @param exceptionList  替换时的例外属性列表
	 * @param target		   新的属性实体
	 * @param source		   要替换的属性实体
	 * @return
	 */
	public static <T> T replaceNotNullFiled(List<String> exceptionList, T target, T source) {
		try {
			for(Field field : target.getClass().getDeclaredFields()) {
				field.setAccessible(true);
				if (field.get(target) == null || ( exceptionList != null 
						&& exceptionList.contains(field.get(target)) )) {
					continue;
				}
				field.set(source, field.get(target));
			}
			return source;
		} catch (Exception e) {
			LOG.debug("将所有非空属性替换异常:{}", e);
			e.printStackTrace();
		}
		return source;
	}
	
	public static List<Class<?>> getAllClassBySuper(Class<?> c) {
		List<Class<?>> returnClassList = null;

		// 获取当前的包名
		String packageName = c.getPackage().getName();
		// 获取当前包下以及子包下所以的类
		List<Class<?>> allClass = getClasses(packageName);
		if (allClass != null) {
			returnClassList = new ArrayList<Class<?>>();
			for (Class<?> classes : allClass) {
				// 判断是否是同一个接口
				if (c.isAssignableFrom(classes)) {
					// 本身不加入进去
					if (!c.equals(classes)) {
						returnClassList.add(classes);
					}
				}
			}
		}

		return returnClassList;
	}

}

